package com.google.appengine.tools.admin;

import com.google.appengine.api.blobstore.BlobInfo;
import com.google.appengine.api.blobstore.BlobInfoFactory;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;

import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Max Ross <max.ross@gmail.com>
 */
public class OnesieServlet extends HttpServlet {

  private final BlobstoreService bs = BlobstoreServiceFactory.getBlobstoreService();

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    Map<String, BlobKey> map = bs.getUploadedBlobs(request);
    String redirectUrl = String.format("/onesie?execute=true&className=%s&blobKey=%s",
        request.getParameter("className"),
        map.values().iterator().next().getKeyString());
    // blob upload callback can only redirect
    response.sendRedirect(redirectUrl);
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    response.setContentType("text/plain");
    if (Boolean.valueOf(request.getParameter("execute"))) {
      // It's a request to execute
      String className = request.getParameter("className");
      BlobKey blobKey = new BlobKey(request.getParameter("blobKey"));
      BlobInfoFactory bif = new BlobInfoFactory();
      BlobInfo info = bif.loadBlobInfo(blobKey);
      byte[] classDef = bs.fetchData(blobKey, 0, info.getSize());
      ClassLoader loader = new ByteClassLoader(classDef);
      final boolean autoFlush = true;
      PrintStream ps = new PrintStream(response.getOutputStream(), autoFlush);
      try {
        Class<?> cls = Class.forName(className, true, loader);
        Method main = cls.getDeclaredMethod("main", new Class[] {String[].class});
        // TODO(maxr): Overload logging to redirect to the servlet response
        System.setOut(ps);
        System.setErr(ps);
        main.invoke(null, new Object[] {new String[0]});
      } catch (Exception e) {
        if (e instanceof RuntimeException) {
          throw (RuntimeException) e;
        }
        throw new RuntimeException(e);
      }
    } else {
      response.getWriter().print(bs.createUploadUrl(request.getParameter("uploadRedirectUrl")));
    }
  }

   private class ByteClassLoader extends URLClassLoader {
    private boolean found = false;
    private final byte[] classDef;

    public ByteClassLoader(byte[] classDef) {
      super(new URL[0], OnesieServlet.this.getClass().getClassLoader());
      this.classDef = classDef;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
      if (found) {
        super.findClass(name);
      }
      try {
        return defineClass(name, classDef, 0, classDef.length);
      } finally {
        found = true;
      }
    }
  }
}
